﻿namespace Refract.UI.Docking
{
    using System;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Input;
    using System.Windows.Media;

    public class DragService
    {
        #region Events

        public event EventHandler<DraggableEventArgs> Dragging;

        /// <summary>
        /// Occurs when [drag started].
        /// </summary>
        public event EventHandler<DraggableEventArgs> DragStarted;

        /// <summary>
        /// Occurs when [drag starting].
        /// </summary>
        public event EventHandler<DraggableEventArgs> DragStarting;

        /// <summary>
        /// Occurs when [drag stopped].
        /// </summary>
        public event EventHandler<DraggableEventArgs> DragStopped;

        /// <summary>
        /// Occurs when [drag stopping].
        /// </summary>
        public event EventHandler<DraggableEventArgs> DragStopping;

        #endregion Events

        #region Properties

        public double GripOffsetX
        {
            get; set;
        }

        public double GripOffsetY
        {
            get; set;
        }

        /// <summary>
        /// Gets or sets a value indicating whether this instance is dragging.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is dragging; otherwise, <c>false</c>.
        /// </value>
        public bool IsDragging
        {
            get; private set;
        }

        /// <summary>
        /// Gets the current position of the captured mouse pointer during dragging.
        /// </summary>
        /// <value>The position.</value>
        public Point Position
        {
            get
            {
                return new Point(X, Y);
            }
        }

        /// <summary>
        /// Gets or sets the X position of the captured mouse pointer during dragging.
        /// </summary>
        /// <value>The X.</value>
        public double X
        {
            get; set;
        }

        /// <summary>
        /// Gets or sets the Y position of the captured mouse pointer during dragging.
        /// </summary>
        /// <value>The Y.</value>
        public double Y
        {
            get; set;
        }

        protected bool DragRequested
        {
            get; set;
        }

        protected FrameworkElement TargetElement
        {
            get; set;
        }

        #endregion Properties

        #region Methods

        /// <summary>
        /// Called after the behavior is attached to an TargetElement.
        /// </summary>
        /// <remarks>Override this to hook up functionality to the TargetElement.</remarks>
        public virtual void Attach(FrameworkElement targetElement)
        {
            TargetElement = targetElement;

            if (TargetElement == null) throw new NullReferenceException("TargetElement");

            TargetElement.MouseLeftButtonUp += AssociatedObject_MouseLeftButtonUp;
            TargetElement.MouseLeftButtonDown += AssociatedObject_MouseLeftButtonDown;
            TargetElement.MouseMove += AssociatedObject_MouseMove;
            TargetElement.LostMouseCapture += AssociatedObject_LostMouseCapture;
            CompositionTarget.Rendering += CompositionTarget_Rendering;
        }

        /// <summary>
        /// Called when the behavior is being detached from its TargetElement, but before it has actually occurred.
        /// </summary>
        /// <remarks>Override this to unhook functionality from the TargetElement.</remarks>
        public virtual void Detach()
        {
            TargetElement.MouseLeftButtonUp -= AssociatedObject_MouseLeftButtonUp;
            TargetElement.MouseLeftButtonDown -= AssociatedObject_MouseLeftButtonDown;
            TargetElement.MouseMove -= AssociatedObject_MouseMove;
            TargetElement.LostMouseCapture -= AssociatedObject_LostMouseCapture;
            CompositionTarget.Rendering -= CompositionTarget_Rendering;
        }

        /// <summary>
        /// Records the current mouse position.
        /// </summary>
        /// <param name="e">The <see cref="System.Windows.Input.MouseEventArgs"/> instance containing the event data.</param>
        protected void RecordMousePosition(MouseEventArgs e)
        {
            var mousePosition = e.GetPosition((UIElement)TargetElement.Parent);

            X = mousePosition.X;
            Y = mousePosition.Y;
        }

        /// <summary>
        /// Starts drag the target element.
        /// </summary>
        protected void StartDrag()
        {
            DragRequested = false;

            DraggableEventArgs eventArgs = new DraggableEventArgs();

            if (DragStarting != null) DragStarting(this, eventArgs);

            if (eventArgs.Cancelled) return;

            IsDragging = true;

            TargetElement.CaptureMouse();
            GripOffsetX = X - Canvas.GetLeft(TargetElement);
            GripOffsetY = Y - Canvas.GetTop(TargetElement);

            IsDragging = true;

            if (DragStarted != null) DragStarted(this, eventArgs);
        }

        /// <summary>
        /// Stops dragging the target element.
        /// </summary>
        protected void StopDrag()
        {
            DraggableEventArgs eventArgs = new DraggableEventArgs();

            if (DragStopping != null) DragStopping(this, eventArgs);
            if (eventArgs.Cancelled) return;

            TargetElement.ReleaseMouseCapture();

            IsDragging = false;

            if (DragStopped != null) DragStopped(this, eventArgs);
        }

        /// <summary>
        /// Handles the LostMouseCapture event of the TargetElement control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Input.MouseEventArgs"/> instance containing the event data.</param>
        void AssociatedObject_LostMouseCapture(object sender, MouseEventArgs e)
        {
            StopDrag();
        }

        /// <summary>
        /// Handles the MouseLeftButtonDown event of the TargetElement control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Input.MouseButtonEventArgs"/> instance containing the event data.</param>
        void AssociatedObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            RecordMousePosition(e);
            DragRequested = true;
        }

        /// <summary>
        /// Handles the MouseLeftButtonUp event of the TargetElement control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Input.MouseButtonEventArgs"/> instance containing the event data.</param>
        void AssociatedObject_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            StopDrag();
        }

        /// <summary>
        /// Handles the MouseMove event of the TargetElement control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Input.MouseEventArgs"/> instance containing the event data.</param>
        void AssociatedObject_MouseMove(object sender, MouseEventArgs e)
        {
            if (!IsDragging && !DragRequested) return;

            var eventArgs = new DraggableEventArgs();

            if(DragRequested)
            {
                StartDrag();

            }

            if (Dragging != null) Dragging(this, eventArgs);

            if (eventArgs.Cancelled) return;

            RecordMousePosition(e);
        }

        /// <summary>
        /// Handles the Rendering event of the CompositionTarget control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        void CompositionTarget_Rendering(object sender, EventArgs e)
        {
            if (IsDragging)
            {
                TargetElement.SetValue(Canvas.LeftProperty, X - GripOffsetX);
                TargetElement.SetValue(Canvas.TopProperty, Y - GripOffsetY);

            }
        }

        #endregion Methods
    }
}